#ifndef INI_FILE_LEXER_H
#define INI_FILE_LEXER_H

#include "IniFileStreamReader.h"
#include "IniFileToken.h"
#include "IniFileErrorID.h"
#include "IniFileHandler.h"
#include <cstring>

namespace IniFile {

class Lexer : public StreamReader
{
	enum State
	{
		STATE_START,
		STATE_NON_QUOTED_BKSLASH,
		STATE_NON_QUOTED_STRING,
		STATE_BKSLASH,
		STATE_STRING
	};
	std::string lexeme_;
	Handler *handler_;
public:
	Lexer(std::istream *inputStream, Handler *handler) :
        StreamReader(inputStream),
        handler_(handler)
	{
	}
	int getToken()
	{
		int c;
		State state = STATE_START;
		lexeme_ = "";
		while ("no token is accepted") {
			c = getChar();
			switch (state)
			{
			case STATE_START:
                if (c == std::istream::traits_type::eof())
                    return TOKEN_EOF;
				switch (c)
				{
				case '[':
				case ']':
					return c;
				case '"':
					state = STATE_STRING;
					break;
				case '=':
				case ':':
					return TOKEN_SEPARATOR;
				case ';':
				case '#':
					return TOKEN_COMMENT_STARTER;
				case '\n':
					return TOKEN_LINE_TERMINATOR;
				case '\\':
					state = STATE_NON_QUOTED_BKSLASH;
					break;
				case ' ':
				case '\t':
					break;
				default:
					lexeme_ += c;
					state = STATE_NON_QUOTED_STRING;
				}
				break;
			case STATE_NON_QUOTED_BKSLASH:
				if (c == std::istream::traits_type::eof())
					return TOKEN_NON_QUOTED_STRING;
                lexeme_ += c;
				state = STATE_NON_QUOTED_STRING;
				break;
			case STATE_NON_QUOTED_STRING:
				if (c == std::istream::traits_type::eof())
					return TOKEN_NON_QUOTED_STRING;
				else if (c == '\\')
					state = STATE_NON_QUOTED_BKSLASH;
				else if (std::strchr(" \t[]\"=:;#\n", c) != NULL) {
				    putBackChar(c);
					return TOKEN_NON_QUOTED_STRING;
				} else {
				    lexeme_ += c;
				}
				break;
			case STATE_BKSLASH:
				if (c == std::istream::traits_type::eof()) {
				    handler_->onError(getRow(), getColumn(),
                                      ERROR_LEXER_UNTERMINATED_STRING, TOKEN_STRING);
					return TOKEN_STRING;
				}
				if (c != '"')
                    lexeme_ += '\\';
                lexeme_ += c;
				state = STATE_STRING;
				break;
			case STATE_STRING:
				if (c == std::istream::traits_type::eof() || c == '\n') {
				    handler_->onError(getRow(), getColumn(),
                                      ERROR_LEXER_UNTERMINATED_STRING, TOKEN_STRING);
                    if (c == '\n')
                        putBackChar(c);
					return TOKEN_STRING;
				} else if (c == '\\')
					state = STATE_BKSLASH;
				else if (c == '"')
					return TOKEN_STRING;
				else {
					lexeme_ += c;
				}
				break;
			}
		}
		return TOKEN_UNKNOWN;
	}
	std::string getLexeme() const
	{
	    return lexeme_;
	}
};

} // namespace IniFile

#endif
